#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/time.h>
#include <sys/unistd.h>

int count1 = 0;

pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;

void *producer1(void *arg) {
    srand(time(NULL));

    int production_max = *(int *) arg;

    for (int i = 0; i < 100; ++i) {
        while (true) {
            pthread_mutex_lock(&mutex1);
            if (count1 < production_max) {
                count1++;
                printf("producer thread %p production product %d\n", pthread_self(), count1);
                pthread_mutex_unlock(&mutex1);
                break;
            }
            pthread_mutex_unlock(&mutex1);
            // usleep(1000 * (rand() % 100));
        }
    }

    return NULL;
}

void *consumer1(void *arg) {
    for (int i = 0; i < 100; ++i) {
        while (true) {
            pthread_mutex_lock(&mutex1);
            if (0 < count1) {
                count1--;
                printf("consumer thread %p consume product %d\n", pthread_self(), count1);
                pthread_mutex_unlock(&mutex1);
                break;
            }
            pthread_mutex_unlock(&mutex1);
            // usleep(1000 * (rand() % 100));
        }
    }

    return NULL;
}

// 仅仅使用单一互斥锁可以实现生产者消费者模型, 但是单一互斥锁不仅会让生产者和消费者之间存在激烈的锁竞争问题
// 不同线程的生产者之间和不同线程的消费者之间也存在激烈的锁竞争问题
void use_mutex_impl_model() {
    struct timeval start, end;
    gettimeofday(&start, NULL);

    int production_max = 1000;

    pthread_t producer_pid[100];
    pthread_t consumer_pid[100];

    for (int i = 0; i < 100; ++i) {
        pthread_create(&producer_pid[i], NULL, producer1, &production_max);
        pthread_create(&consumer_pid[i], NULL, consumer1, NULL);
    }

    for (int i = 0; i < 100; ++i) {
        pthread_join(producer_pid[i], NULL);
        pthread_join(consumer_pid[i], NULL);
    }

    gettimeofday(&end, NULL);
    int use_time = 1000000 * (end.tv_sec - start.tv_sec) + end.tv_usec - start.tv_usec;

    printf("mutex use time: %d\n", use_time);
}

int count2 = 0;

pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;

pthread_cond_t cond1 = PTHREAD_COND_INITIALIZER;

void *producer2(void *arg) {
    srand(time(NULL));
    int production_max = *(int *) arg;

    for (int i = 0; i < 100; ++i) {
        pthread_mutex_lock(&mutex2);
        while (true) {
            if (count2 < production_max) {
                count2++;
                printf("producer thread %p production product %d\n", pthread_self(), count2);
                pthread_cond_signal(&cond1);
                break;
            }
            pthread_cond_wait(&cond1, &mutex2);
            // usleep(1000 * (rand() % 100));
        }
        pthread_mutex_unlock(&mutex2);
    }

    return NULL;
}

void *consumer2(void *arg) {
    srand(time(NULL));

    for (int i = 0; i < 100; ++i) {
        pthread_mutex_lock(&mutex2);
        while (true) {
            if (0 < count2) {
                count2--;
                printf("consumer thread %p consume product %d\n", pthread_self(), count2);
                pthread_cond_signal(&cond1);
                break;
            }
            pthread_cond_wait(&cond1, &mutex2);
            // usleep(1000 * (rand() % 100));
        }
        pthread_mutex_unlock(&mutex2);
    }

    return NULL;
}

// 使用单一条件变量控制生产者和消费者线程的同步时,
// 存在生产者唤醒生产者、消费者唤醒消费者的情况
// 但是上述的两种情况不会影响程序的正常执行, 同类唤醒后循环判断会再次陷入休眠
void use_single_cond_impl_model() {
    struct timeval start, end;
    gettimeofday(&start, NULL);

    int production_max = 1000;

    pthread_t producer_pid[100];
    pthread_t consumer_pid[100];

    for (int i = 0; i < 100; ++i) {
        pthread_create(&producer_pid[i], NULL, producer2, &production_max);
        pthread_create(&consumer_pid[i], NULL, consumer2, NULL);
    }

    for (int i = 0; i < 100; ++i) {
        pthread_join(producer_pid[i], NULL);
        pthread_join(consumer_pid[i], NULL);
    }

    gettimeofday(&end, NULL);
    int use_time = 1000000 * (end.tv_sec - start.tv_sec) + end.tv_usec - start.tv_usec;

    printf("single cond use time: %d\n", use_time);
}

int count3 = 0;

pthread_mutex_t mutex3 = PTHREAD_MUTEX_INITIALIZER;

pthread_cond_t cond2 = PTHREAD_COND_INITIALIZER;

pthread_cond_t cond3 = PTHREAD_COND_INITIALIZER;

void *producer3(void *arg) {
    srand(time(NULL));
    int production_max = *(int *) arg;

    for (int i = 0; i < 100; ++i) {
        pthread_mutex_lock(&mutex3);
        while (true) {
            if (count2 < production_max) {
                count2++;
                printf("producer thread %p production product %d\n", pthread_self(), count2);
                pthread_cond_signal(&cond3);
                break;
            }
            pthread_cond_wait(&cond2, &mutex3);
            // usleep(1000 * (rand() % 100));
        }
        pthread_mutex_unlock(&mutex3);
    }

    return NULL;
}

void *consumer3(void *arg) {
    srand(time(NULL));

    for (int i = 0; i < 100; ++i) {
        pthread_mutex_lock(&mutex3);
        while (true) {
            if (0 < count2) {
                count2--;
                printf("consumer thread %p consume product %d\n", pthread_self(), count2);
                pthread_cond_signal(&cond2);
                break;
            }
            pthread_cond_wait(&cond3, &mutex3);
            // usleep(1000 * (rand() % 100));
        }
        pthread_mutex_unlock(&mutex3);
    }

    return NULL;
}

// 使用两个条件变量控制生产者和消费者线程的同步时,
// 生产者会唤醒消费者, 消费者会唤醒生产者, 多线程锁资源竞争不会太激烈
void use_double_cond_impl_model() {
    struct timeval start, end;
    gettimeofday(&start, NULL);

    int production_max = 1000;

    pthread_t producer_pid[100];
    pthread_t consumer_pid[100];

    for (int i = 0; i < 100; ++i) {
        pthread_create(&producer_pid[i], NULL, producer3, &production_max);
        pthread_create(&consumer_pid[i], NULL, consumer3, NULL);
    }

    for (int i = 0; i < 100; ++i) {
        pthread_join(producer_pid[i], NULL);
        pthread_join(consumer_pid[i], NULL);
    }

    gettimeofday(&end, NULL);
    int use_time = 1000000 * (end.tv_sec - start.tv_sec) + end.tv_usec - start.tv_usec;

    printf("double cond use time: %d\n", use_time);
}

int main() {
    // use_mutex_impl_model();
    // use_single_cond_impl_model();
    use_double_cond_impl_model();
}